﻿namespace TanzimSaqib.NoBrainer
{
    using System.Reflection;
    using System.Xml.Linq;
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web.UI;
    using System.Web.UI.HtmlControls;
    using System.Web.UI.WebControls;

    internal static class ViewUtilities
    {
        #region Properties

        private const string XML_TEMPLATE = "<?xml version=\"1.0\" encoding=\"utf-8\" ?><Root>{0}</Root>";
        private static readonly List<ViewControl> _ControlList = new List<ViewControl>();

        private static readonly string[] _ValidPropertyNames = new[]
                                                               {
                                                                   Constants.ErrorMessage,
                                                                   Constants.Model,
                                                                   Constants.ModelList,
                                                                   Constants.ModelError,
                                                                   Constants.CmsError
                                                               };

        private static readonly string[] _PropertiesForInternalUse = new[]
                                                               {
                                                                   Constants.ErrorMessages, 
                                                                   Constants.CmsErrorMessages,
                                                                   Constants.IsValid
                                                               };
        #endregion

        #region Func

        public static readonly Func<string, bool> IsPropertyForInternalUse =
            check => _PropertiesForInternalUse.Any(item => string.Compare(item, check, StringComparison.InvariantCulture) == 0);

        #endregion

        #region Public methods

        public static List<ViewControl> BuildControlGraph<V>(Control root, V viewModel) where V : ModelBase
        {
            _ControlList.Clear();
            BuildControlList(root);
            return _ControlList;
        }

        public static V Bind<V>(Control root, V viewModel, Lookup<string, ViewControl> controlGraph, PropertyInfo[] userProperties) where V : ModelBase
        {
            foreach (var property in userProperties)
            {
                // We're skipping error messages here, because they will have their chances later in Phase 3.
                BuildViewModel(property, viewModel, controlGraph);
            }

            return viewModel;
        }

        public static void BindView(Control root, ModelBase viewModel, Lookup<string, ViewControl> controlGraph, PropertyInfo[] userProperties)
        {
            var properties = viewModel.GetType().GetProperties();

            foreach (var property in properties)
            {
                var propertyValue = property.GetValue(viewModel, null);

                if (propertyValue == null)
                    continue;

                var argumentType = propertyValue.GetType().GetGenericArguments();

                foreach (var control in new[] { Constants.Model, Constants.ModelList }.Select(viewItem => controlGraph[viewItem]).Where(controls => controls.Count() >= 1).SelectMany(controls => controls.Where(control => control.ModelPropertyValue.Equals(property.Name))))
                {
                    if (argumentType.Count() > 0)
                    {
                        // Set List/Collection items
                        if (typeof(ModelListItemBase).Equals(argumentType[0].BaseType))
                        {
                            SetControlValueObject(root.FindControlRecursively(control.ActualControl.ID), propertyValue);
                        }
                    }
                    else
                    {
                        SetControlValue(root.FindControlRecursively(control.ActualControl.ID), string.Format(control.ModelPropertyFormat ?? "{0}", propertyValue));
                    }
                }
            }
        }

        public static void BindErrors(Control root, ModelBase viewModel, Lookup<string, ViewControl> controlGraph)
        {
            var cmsErrorControls = controlGraph[Constants.CmsError];
            var errorControls = controlGraph[Constants.ModelError];
            var modelControls = controlGraph[Constants.Model];

            // Hide all CMS-Error bound controls, first.
            foreach (var cmsErrorControl in cmsErrorControls)
            {
                cmsErrorControl.ActualControl.Visible = false;
            }

            if (!string.IsNullOrEmpty(viewModel.ErrorMessage))
            {
                foreach (var control in modelControls.Where(control => control.ModelProperty.Equals(Constants.Model) && control.ModelPropertyValue.Equals(Constants.ErrorMessage)))
                {
                    SetControlValue(control.ActualControl, viewModel.ErrorMessage);
                }
            }

            ClearErrorControls(controlGraph);

            if ((viewModel.ErrorMessages == null || viewModel.ErrorMessages.Count <= 0) && (viewModel.CmsErrorMessages == null || viewModel.CmsErrorMessages.Count <= 0))
                return;

            foreach (var errorMessage in viewModel.ErrorMessages)
            {
                var message = errorMessage;

                foreach (var control in errorControls.Where(control => control.ModelPropertyValue == message.Key))
                {
                    SetControlValue(control.ActualControl, errorMessage.Value);
                }
            }

            // Show selective CMS-Error bound controls that are directed so
            if(viewModel.CmsErrorMessages.Count > 0)
            {
                foreach (var errorMessage in viewModel.CmsErrorMessages)
                {
                    foreach (var cmsErrorControl in
                        cmsErrorControls.Where(cmsErrorControl => cmsErrorControl.ModelPropertyValue.Equals(errorMessage.Key)))
                    {
                        cmsErrorControl.ActualControl.Visible = errorMessage.Value;
                    }
                }   
            }
        }

        public static Dictionary<string, ViewCmsControl> GetViewCmsControls(XElement xml, string malformedPagePath)
        {
            var controls = new Dictionary<string, ViewCmsControl>();
            var legalPath = malformedPagePath.Remove(0, 4).Replace("_aspx", ".aspx").Replace("_", "\\");

            XElement contentViewTag = null;
            var contentViews = xml.Elements(Constants.XmlTagView);

            foreach (var contentView in from contentView in contentViews
                        let attribs = contentView.Attributes()
                            from attr in attribs.Where(attr => attr.Name.LocalName.Equals(Constants.XmlTagPageName, StringComparison.InvariantCultureIgnoreCase))
                                .Where(attr => attr.Value.Equals(legalPath, StringComparison.InvariantCultureIgnoreCase))
                            select contentView)
            {
                contentViewTag = contentView;
            }

            if (contentViewTag != null && !contentViewTag.IsEmpty)
            {
                foreach (var content in contentViewTag.Elements(Constants.XmlTagContent).Where(content => content != null && !content.IsEmpty))
                {
                    var control = new ViewCmsControl
                    {
                        ID = content.Attribute(Constants.XmlTagId).Value,
                        Content = content.InnerXml()
                    };

                    if (content.Attribute(Constants.XmlTagCssClass) != null && !string.IsNullOrEmpty(content.Attribute(Constants.XmlTagCssClass).Value))
                        control.CssClass = content.Attribute(Constants.XmlTagCssClass).Value;

                    controls.Add(content.Attribute(Constants.XmlTagId).Value, control);
                }
            }

            return controls;
        }

        public static void BindCmsInputControls(Control root, Dictionary<string, ViewCmsControl> cms)
        {
            foreach (var ViewCmsControl in cms)
            {
                var control = root.FindControlRecursively(ViewCmsControl.Value.ID);
                ListControl collectionControl;

                // both Radio + DropDown
                if (control is ListControl)
                {
                    collectionControl = control as ListControl;

                    collectionControl.Items.Clear();

                    var listItems = GetInnerContentXml(ViewCmsControl.Value.Content);
                    foreach (var listItem in listItems)
                    {
                        collectionControl.Items.Add(new ListItem()
                        {
                            Text = listItem.Value,
                            Value = listItem.Attribute("value").Value
                        });
                    }
                }
            }
        }

        #endregion

        #region Private methods

        private static void BuildViewModel(PropertyInfo property, ModelBase viewModel, Lookup<string, ViewControl> controlGraph)
        {
            foreach (var controls in new[] { Constants.Model, Constants.ModelList }.Select(viewItem => controlGraph[viewItem]))
            {
                if (controls.Count() < 1) continue;

                foreach (var control in controls)
                {
                    if (!control.ModelPropertyValue.Equals(property.Name)) continue;

                    var content = string.Empty;

                    if (control.ActualControl is TextBox)
                    {
                        content = (control.ActualControl as TextBox).Text;
                    }
                    else if (control.ActualControl is HtmlInputText)
                    {
                        content = (control.ActualControl as HtmlInputText).Value;
                    }
                    else if (control.ActualControl is HtmlInputRadioButton)
                    {
                        content = System.Web.HttpContext.Current.Request[(control.ActualControl as HtmlInputRadioButton).Name];
                    }

                    // both Radio & DropDown
                    else if (control.ActualControl is ListControl)
                    {
                        content = (control.ActualControl as ListControl).SelectedValue;
                    }
                    else if (control.ActualControl is CheckBox)
                    {
                        content = (control.ActualControl as CheckBox).Checked.ToString();
                    }
                    else if (control.ActualControl is LiteralControl
                        || control.ActualControl is Label
                        || control.ActualControl is HtmlGenericControl)
                    {
                        // not an input control, continue
                        continue;
                    }

                    // Check if this is of primitive data types (eg. string, int, bool, double, etc.)
                    if (property.PropertyType.Equals(typeof(int)))
                    {
                        property.SetValue(viewModel, !string.IsNullOrEmpty(content) ? int.Parse(content) : 0, null);
                    }
                    else if (property.PropertyType.Equals(typeof(double)))
                    {
                        property.SetValue(viewModel, !string.IsNullOrEmpty(content) ? double.Parse(content) : 0.0, null);
                    }
                    else if (property.PropertyType.Equals(typeof(bool)))
                    {
                        property.SetValue(viewModel, !string.IsNullOrEmpty(content) ? bool.Parse(content) : false, null);
                    }
                    else
                    {
                        // if it is a string
                        if (property.PropertyType.Equals(typeof(string)))
                            property.SetValue(viewModel, content, null);
                    }
                }
            }
        }

        private static void SetControlValueObject(Control foundControl, object propertyValue)
        {
            if (foundControl == null)
                return;

            if (foundControl is GridView)
            {
                var grid = (foundControl as GridView);

                grid.DataSource = propertyValue;
                grid.DataBind();
            }
            else if (foundControl is DropDownList)
            {
                var ddl = (foundControl as DropDownList);
                ddl.DataSource = propertyValue;
                ddl.DataBind();
            }
        }

        private static void ClearErrorControls(ILookup<string, ViewControl> controlGraph)
        {
            foreach (var control in controlGraph[Constants.ModelError])
            {
                SetControlValue(control.ActualControl, string.Empty);
            }
        }

        private static void SetControlValue(Control foundControl, string content)
        {
            if (foundControl == null)
                return;

            if (foundControl is LiteralControl)
            {
                (foundControl as LiteralControl).Text = content;
            }
            else if (foundControl is Label)
            {
                (foundControl as Label).Text = content;
            }
            else if (foundControl is HtmlGenericControl)
            {
                (foundControl as HtmlGenericControl).InnerText = content;
            }
            else if (foundControl is TextBox)
            {
                (foundControl as TextBox).Text = content;
            }

            // input controls: to persist states
            // radio + DropDown
            else if (foundControl is ListControl)
            {
                (foundControl as ListControl).SelectedValue = content;
            }
        }

        private static List<string> GetBoundProperties(AttributeCollection attributes)
        {
            return _ValidPropertyNames.Where(validPropertyName => attributes[validPropertyName] != null).ToList();
        }

        private static Control BuildControlList(Control root)
        {
            if (root is WebControl)
            {
                var webControl = root as WebControl;
                var properties = GetBoundProperties(webControl.Attributes);

                if (properties.Count > 0)
                {
                    foreach (var property in properties)
                    {
                        _ControlList.Add(
                            new ViewControl
                            {
                                ActualControl = root,
                                ModelProperty = property,
                                ModelPropertyValue = webControl.Attributes[property],
                                ModelPropertyFormat = webControl.Attributes[Constants.FormatWith]
                            });
                    }
                }
            }
            else if (root is HtmlControl)
            {
                var htmlControl = root as HtmlControl;
                var properties = GetBoundProperties(htmlControl.Attributes);

                if (properties.Count > 0)
                {
                    foreach (var property in properties)
                    {
                        _ControlList.Add(
                            new ViewControl
                            {
                                ActualControl = root,
                                ModelProperty = property,
                                ModelPropertyValue = htmlControl.Attributes[property],
                                ModelPropertyFormat = htmlControl.Attributes[Constants.FormatWith]
                            });
                    }
                }
            }

            return (from Control control in root.Controls select BuildControlList(control)).FirstOrDefault(t => t != null);
        }

        private static Control FindControlRecursively(this Control root, string id)
        {
            return root.ID == id ? root : 
                (from Control control in root.Controls select control.FindControlRecursively(id))
                    .FirstOrDefault(foundControl => foundControl != null);
        }

        private static IEnumerable<XElement> GetInnerContentXml(string content)
        {
            return XElement.Parse(string.Format(XML_TEMPLATE, content)).Elements("ListItem");
        }

        #endregion
    }
}
